#include "StdAfx.h"
#include "SpecifiedChunk.h"
#include "ChunkSpace.h"
#include "ChunkFileSerializer.h"
#include "ChunkIdentifier.h"

#include "ChunkManager.h"

#include "Hull.h"
#include "ChunkHull.h"

#include <algorithm>
#include "3rd_part\xml\TiXmlUtility.h"


using namespace ChunkSystem;
using namespace Common;

template< class Unit, int SPAN >
void TGrid<Unit,SPAN>::SetOrigin( int16 x , int16 y )
{
	// move y to the right position
	while ( m_centerGridPos.second < y )
	{
		for (int x = 0; x < SPANM; x++) 
			this->_DeleteEntry( x, m_centerGridPos.second - SPANH );
		m_centerGridPos.second++;
	}
	while ( m_centerGridPos.second > y )
	{
		for (int x = 0; x < SPANM; x++) 
			this->_DeleteEntry( x, m_centerGridPos.second + SPANH );
		m_centerGridPos.second--;
	}

	// move x to the right position
	while ( m_centerGridPos.first < x )
	{
		for (int y = 0; y < SPANM; y++ ) 
			this->_DeleteEntry( m_centerGridPos.first - SPANH, y );
		m_centerGridPos.first++;
	}
	while ( m_centerGridPos.first > x )
	{
		for (int y = 0; y < SPANM; y++ ) 
			this->_DeleteEntry( m_centerGridPos.first + SPANH, y );
		m_centerGridPos.first--;
	}
}

template< class Unit, int SPAN >
void TGrid<Unit,SPAN>::_DeleteEntry( int16 x , int16 y )
{
	Unit*& pUnit = this->operator()( x, y );
	if( pUnit != NULL )
	{
		delete pUnit;
		pUnit = NULL;
	}
}

template< class Unit, int SPAN >
Unit*& TGrid<Unit,SPAN>::operator()( int16 x , int16 y )
{
	return m_Units[ SPAN & y ][ SPAN & x ];
}

template< class Unit, int SPAN >
const Unit* TGrid<Unit,SPAN>::operator()( int16 x , int16 y ) const
{
	return m_Units[ SPAN & y ][ SPAN & x ];
}

///////////////////////////////////////////////////////////////////////////////////////////////
Column::Column()
	:m_pExternalChunk( NULL )
{
}

Column::~Column()
{
	//delete chunks in
}

Chunk* Column::FindChunk(const Common::Vector3& vTarPos, Chunk* pIgnore ) const
{
	Chunk* pChunk = m_pExternalChunk;

	const ChunkHull* pFound = static_cast< const ChunkHull* >( m_HullTree.TestPoint( vTarPos ) );

	while (pFound != NULL)
	{
		if ( ( pFound->GetChunk() == m_pExternalChunk || pChunk->GetAABB().volume() > pFound->GetChunk()->GetAABB().volume() )
			&& pFound->GetChunk()->GetAABB().intersects( vTarPos ) 
			&& pIgnore != pFound->GetChunk() )
			
			pChunk = pFound->GetChunk();

		pFound = static_cast< const ChunkHull* >( pFound->pNext_ );
	}

	return pChunk;
}


void Column::AddChunk( HullBorder& rBorders , Chunk* pChunk )
{
	if( pChunk == NULL )
		return;

	ExternalChunk* pExternalChunk = dynamic_cast< ExternalChunk* >( pChunk );
	if( pExternalChunk != NULL )
		m_pExternalChunk = pExternalChunk;

	InternalChunk* pInternalChunk = dynamic_cast< InternalChunk* >( pChunk );
	if( pInternalChunk == NULL )
		return;

	ChunkHull* pHullContent = new ChunkHull(pInternalChunk);
	m_HullTree.Add( rBorders , pHullContent, &pInternalChunk->GetHullOutLines() );

	m_holdingChunks.push_back( pHullContent );
}


ChunkSpace::ChunkSpace(const String& strSpaceName )
	:m_strSpaceName(strSpaceName),m_fGridResolution(128.0f),m_fFocusRange(200.0f)
{
	m_pSky = new HeavenChunk(this);
	m_pEarth = new HellChunk(this);
}

ChunkSpace::~ChunkSpace(void)
{
	CurrentChunkMap::iterator pos = m_CurrentChunkMap.begin();
	for( ; pos != m_CurrentChunkMap.end(); ++pos )
		delete pos->second;
	m_CurrentChunkMap.clear();
}

ChunkSpace::CurrentChunkMap& ChunkSpace::GetCurrentChunk(void)
{
	return m_CurrentChunkMap;
}

Chunk* ChunkSpace::GetHeaven(void)
{
	return m_pSky;
}

Chunk* ChunkSpace::GetEarth(void)
{
	return m_pEarth;
}

void ChunkSpace::AddIdenticalChunk( Chunk*& pNewChunk )
{
	if( pNewChunk == NULL )
		return;

	if( pNewChunk == m_pSky || pNewChunk == m_pEarth )
		return;

	CurrentChunkMap::iterator pos = m_CurrentChunkMap.find( pNewChunk->GetId() );
	if( pos != m_CurrentChunkMap.end() )
	{
		if( pNewChunk != pos->second )
		{
			delete pNewChunk;
			pNewChunk = pos->second;
		}
	}
	else
	{
		m_CurrentChunkMap.insert( CurrentChunkMap::value_type( pNewChunk->GetId() , pNewChunk ) );

		//register in grid
		ExternalChunk* pExternalChunk = static_cast<ExternalChunk*>(pNewChunk);
		if ( pExternalChunk )
		{
			GridCoord chunkCoord = this->PosToGridCoord( pExternalChunk->GetWorldTM().getTrans() );
			if( m_GridChunkMap.find( chunkCoord ) != m_GridChunkMap.end() )
				return;

			m_GridChunkMap[chunkCoord] = pExternalChunk;
		}

		//std::for_each(m_conListeners.begin(),m_conListeners.end(),
			//std::bind2nd( std::mem_fun<void,IChunkSpaceEventListener>( &IChunkSpaceEventListener::OnAddIdenticalChunk ),pNewChunk) );
	}
	//pChunk->ratify();
}

void ChunkSpace::RemoveChunk( Chunk* pChunk )
{
	if( pChunk == NULL )
		return;

	CurrentChunkMap::iterator pos = m_CurrentChunkMap.find( pChunk->GetId() );
	if( pos != m_CurrentChunkMap.end() )
	{
		m_CurrentChunkMap.erase(pos);
		
		std::for_each(m_conListeners.begin(),m_conListeners.end(),
			std::bind2nd( std::mem_fun<void,IChunkSpaceEventListener>( &IChunkSpaceEventListener::OnRemoveChunk ),pChunk) );
	}

	GridCoord chunkCoord = this->PosToGridCoord( pChunk->GetWorldTM().getTrans() );
	GridChunkMap::iterator gridPos = m_GridChunkMap.find( chunkCoord );
	if( gridPos != m_GridChunkMap.end() )
		m_GridChunkMap.erase( gridPos );

	//delete pChunk;
}

Column* ChunkSpace::GetColumn( const Common::Vector3& vPos , bool bCreateIfNotFound )
{
	GridCoord rGridCoord = this->PosToGridCoord( vPos );
	if(!m_FocusGrid.InRange(rGridCoord.first, rGridCoord.second))
		return NULL;

	Column*& pCol = m_FocusGrid(rGridCoord.first, rGridCoord.second);
	if( pCol == NULL && bCreateIfNotFound )
		pCol = new Column;

	return pCol;
}


Chunk* ChunkSpace::GetChunkByGridCoord( const GridCoord& rCoord )
{
	GridChunkMap::iterator pos = m_GridChunkMap.find( rCoord );
	if( pos == m_GridChunkMap.end() )
		return NULL;

	return pos->second;
}

Chunk* ChunkSpace::GetChunkByPointTest( const Common::Vector3& pos )
{
	Column* pChunkCol = this->GetColumn( pos );
	if( pChunkCol == NULL )
		return NULL;

	return pChunkCol->FindChunk( pos );
}

Chunk* ChunkSpace::CreateChunkFromChunkFile( const Common::Vector3& pos, ChunkFileSerializer* pFileSerializer )
{
	GridCoord targetPos = this->PosToGridCoord( pos );
	String strExternalChunk = ChunkIdentifier::GridCoordToExternalChunkPath(targetPos);
	String strExternalChunkPath;

	std::stringstream ss;
	ss << ChunkManager::GetInstance().GetResPath() << "/" << m_strSpaceName << "/" << strExternalChunk << ".chunk";
	ss >> strExternalChunkPath;

	TiXmlElement* pChunkRoot = pFileSerializer->GetChunkRoot( strExternalChunkPath );
	if( pChunkRoot == NULL )
		return NULL;

	//search best fit chunk
	String strFitBestChunk = strExternalChunk;
	TiXmlHandle rootHandle(pChunkRoot);

	float minVolume = FLT_MAX;

	AxisAlignedBox bestChunkAabb;
	Matrix4 bestChunkTransform;

	String strChunkTypeIdentifier =	pFileSerializer->GetTag( ChunkFileSerializer::eChunk );
	for( int i = 0 ; ; i++)
	{
		TiXmlHandle pChunkItem = rootHandle.Child( strChunkTypeIdentifier , i );
		if( pChunkItem.ToElement() == NULL )
			break;

		//read transform
		TiXmlHandle pTransformNode = pChunkItem.FirstChild("transform");
		if( pTransformNode.ToElement() == NULL )
			continue;
		Matrix4 matTransform = TiXmlUtility::ReadMatrix(pTransformNode);

		//read aabb
		TiXmlHandle pAABBNode = pChunkItem.FirstChild("aabb");
		if( pAABBNode.ToElement() == NULL )
			continue;
		AxisAlignedBox aabb = TiXmlUtility::ReadAABB(pAABBNode);

		//transform aabb to world space
		aabb.transform(matTransform);

		if( !aabb.intersects( pos ) )
			continue;
		float fTargetChunkVolume = aabb.volume();
		if( fTargetChunkVolume > minVolume )
			continue;
		minVolume = fTargetChunkVolume;

		//read name
		TiXmlHandle pResource = pChunkItem.FirstChild("resource");
		if( pResource.ToElement() == NULL )
			continue;
		strFitBestChunk = pResource.ToElement()->Value();

		bestChunkAabb = aabb;
		bestChunkTransform = matTransform;
	}

	Chunk* pFitBestChunk = NULL;
	//identify by the name
	if( *strFitBestChunk.rbegin() == 'o' )
	{
		pFitBestChunk = new ExternalChunk(strExternalChunk ,this);
	}
	else
	{
		//pFitBestChunk = new InternalChunk(strFitBestChunk, this, bestChunkTransform, bestChunkAabb, );
	}

	return pFitBestChunk;
}

GridCoord ChunkSpace::PosToGridCoord( const Vector3& rPos ) const
{
	int	cx = int(rPos.x / m_fGridResolution);
	int	cz = int(rPos.z / m_fGridResolution);

	//if (rPos.x < 0.f) cx--;
	//if (rPos.z < 0.f) cz--;

	return GridCoord( cx , cz );
}

Vector3 ChunkSpace::GridCoordToPos( const GridCoord& rGridCoord )
{
	return Vector3( rGridCoord.first * m_fGridResolution , 0.0f , rGridCoord.second * m_fGridResolution );
}

void ChunkSpace::NoticeChunk( Chunk* pBindedChunk )
{
	if( pBindedChunk == NULL || pBindedChunk->GetCurState() != Chunk::eBinded )
		return;

	BlurredChunks::iterator pos = std::find( m_blurredChunks.begin(), m_blurredChunks.end(), pBindedChunk );
	if( pos != m_blurredChunks.end() )
		return;
	
	m_blurredChunks.push_back( pBindedChunk );
}

void ChunkSpace::IgnoreChunk( Chunk* pIgnoreChunk )
{

}

void ChunkSpace::Focus( const Vector3& point )
{
	//calculate new origin
	GridCoord rCenterGridCoord = this->PosToGridCoord( point );

	//reset origin
	m_FocusGrid.SetOrigin( rCenterGridCoord.first, rCenterGridCoord.second );


	/*
	// also delete any columns that are stale
	for (int x = cx - FocusGrid::SPANH; x <= cx + FocusGrid::SPANH; x++)
	{
	for (int z = cz - FocusGrid::SPANH; z <= cz + FocusGrid::SPANH; z++)
	{
	Column* pColumn = m_FocusGrid( x, z );
	if (pColumn != NULL && pColumn->isStale())
	{
	m_FocusGrid.DeleteEntry( x, z );
	}
	}
	}
	*/

	int iOldBlurredChunkSize = m_blurredChunks.size();

	int iFocusRange = (int)( m_fFocusRange / m_fGridResolution );

	//g_CSCTimeLimit = timestamp() + stampsPerSecond() * 2 / 1000;
	//bool hitTimeLimit = false;

	// focus any chunks that are now in range
	for ( int iBlurChunkIdx = 0 ; iBlurChunkIdx != m_blurredChunks.size() ; ++iBlurChunkIdx )
	{
		Chunk* pChunk = m_blurredChunks[iBlurChunkIdx];

		//calculate distance between blur chunks and new center
		const Vector3& vCenter = pChunk->GetWorldTM().getTrans();
		GridCoord rChunkGridCoord = this->PosToGridCoord( vCenter );

		int	iXDistInGridSpace = rChunkGridCoord.first - rCenterGridCoord.first;
		int iZDistInGridSpace = rChunkGridCoord.second - rCenterGridCoord.second;

		if (-iFocusRange <= iXDistInGridSpace && iXDistInGridSpace <= iFocusRange &&
			-iFocusRange <= iZDistInGridSpace && iZDistInGridSpace <= iFocusRange )
		{
			/*
			// skip this one if we're outta time
			if (hitTimeLimit && (
			nx <= -(FOCUS_RANGE-1) || nx >= (FOCUS_RANGE-1) ||
			nz <= -(FOCUS_RANGE-1) || nz >= (FOCUS_RANGE-1))) continue;
			*/

			// this chunk is no longer blurred
			m_blurredChunks.erase( m_blurredChunks.begin() + (iBlurChunkIdx--) );

			/*
			// see if this chunk is new to (nx,nz) and adjacent columns,
			// close them if it isn't
			for (int x = nx - 1; x <= nx + 1; x++) for (int z = nz - 1; z <= nz + 1; z++)
			{
			Column * pCol = currentFocus_( cx + x, cz + z );
			if (pCol != NULL) pCol->shutIfSeen( pChunk );
			}
			*/

			// do the actual focussing work
			pChunk->Focus();
			// it's ok for a chunk to re-add itself on failure to focus,
			// 'coz it'll go to the end of our vector (not that chunks
			// currently ever fail to focus)

			/*
			// open all the columns, and mark them as having seen this chunk
			for (int x = nx - 1; x <= nx + 1; x++) for (int z = nz - 1; z <= nz + 1; z++)
			{
			Column * pCol = currentFocus_( cx + x, cz + z );
			if (pCol != NULL) pCol->openAndSee( pChunk );
			}
			*/
			// get out now if we're an edge chunk
			//if (timestamp() > g_CSCTimeLimit) hitTimeLimit = true;
		}

	}

	/*
	// if we focussed any chunks then see if any homeless items
	// would prefer to live in them now instead
	if (iOldBlurredChunkSize != m_blurredChunks.size())
	{
	for (int i = int(homeless_.size()-1); i >= 0; i--)
	{
	i = std::min( i, int(homeless_.size()-1) );
	ChunkItemPtr pHomelessItem = homeless_[i];
	pHomelessItem->nest( static_cast<ChunkSpace*>( this ) );
	}
	}
	*/
}

void ChunkSpace::RegisterListener( IChunkSpaceEventListener* pListener )
{
	ItrChunkSpaceEventListeners pos = std::find( m_conListeners.begin(), m_conListeners.end(), pListener );
	if( pos != m_conListeners.end() )
		return;

	m_conListeners.push_back(pListener);
}

void ChunkSpace::UnregisterListener( IChunkSpaceEventListener* pListener )
{
	ItrChunkSpaceEventListeners pos = std::find( m_conListeners.begin(), m_conListeners.end(), pListener );
	if( pos == m_conListeners.end() )
		return;

	m_conListeners.erase(pos);
}

